/*
Copyleft 2006 by T.S.Yo
*/

package exp.util;
import ec.*;
import ec.coevolve.*;
import ec.simple.*;
import ec.util.*;
//import ec.gp.*;
//import ec.gp.koza.*;

/* 
 * MyExpEvaluator.java
 * 
 * Created: 2006.11.24
 * By: T.S.Yo
 */

/**
 *
 * @author T.S.Yo
 * @version 1.0 
 */

public class MyExpEvaluator extends Evaluator
{
    public int ntp ;                        // number of test-points
    public int nsol;                        // number of solutions
    protected float[][] interaction;        // interaction matrix
        
    // Select test points for evaluation. Options including: 
    //    1) Fixed random sampling
    //    2) New random sampling
    //    3) All test points
    //    4) Coevolution 
    public int STPflag;
    public static final String TPOINTS = "SelectTestPoints";
    public static final int TPOINT_FIXED_RANDOM =1;
    public static final int TPOINT_NEW_RANDOM   =2;
    public static final int TPOINT_ALL          =3;
    public static final int TPOINT_COEVOLVUTION =4;
    // Define the scoring method. Options including: 
    //    1) Average score 
    //    2) Weighted score
    //    3) MOO score
    //    4) Similarity
    //    5) Average Informativeness
    //    6) Weighted Informativeness
    public int SCOflag;
    public static final int SCORE_AVERAGE    =1;
    public static final int SCORE_WEIGHTED   =2;
    public static final int SCORE_MOO        =3;
    public static final int SCORE_SIMILARITY =4;
    public static final int SCORE_AVE_INFO   =5;
    public static final int SCORE_WEI_INFO   =6;
    // Clone of previous population
    public Individual[] iniTP;
    public float[] finalEval;
//----------------------------------------------------------------------
// Madotary functions
    // Setup
    public void setup( final EvolutionState state, final Parameter base )
    {
        super.setup( state, base );

        // Parameters for Experiment
        MyExpParam exppar = new MyExpParam();
        STPflag = exppar.readSTPflag(state,base);
        SCOflag = exppar.readSCOflag(state,base);
        
        // Parameters for coevolution
        // At this point, we do not know the number of subpopulations, 
        //  so we read it as well from the parameters file
        Parameter tempSubpop = new Parameter( ec.Initializer.P_POP ).push( ec.Population.P_SIZE );
        int numSubpopulations = state.parameters.getInt( tempSubpop, null, 0 );
        // Check number of population : 2
        if( numSubpopulations != 2 )
            state.output.fatal( "Parameter incorrect, number of subpopulations has to be 2.", tempSubpop );
            
        // 1st subpopulation: test-points
        ntp = state.parameters.getInt( tempSubpop.pop().push("subpop.0").push("size") , null, 0 );
        if( ntp < 0 )
            state.output.fatal( "Parameter not found, or it has an incorrect value.", 
                                tempSubpop.push("0").push("size") );
        // 2nd subpopulation: solutions
        nsol = state.parameters.getInt( tempSubpop.pop().push("subpop.1").push("size") , null, 0 );
        if( nsol < 0 )
            state.output.fatal( "Parameter not found, or it has an incorrect value.", 
                                tempSubpop.push("1").push("size") );
        //System.out.println("ntp: "+ntp+"    nsol: "+nsol);
        // Initialize interaction matrix
        interaction = new float[ntp][nsol];
    }

    // Returns true if the run should end right now
    public boolean runComplete( final EvolutionState state )
    {    return false;}

    // Evaluates the fitness of an entire population
    public void evaluatePopulation(final EvolutionState state)
    {
        // Preprocessing
        //beforeEvaluation( state, state.population, (GroupedProblemForm)p_problem );
        // Evaluate
        ((GroupedProblemForm)p_problem).preprocessPopulation(state,state.population);
        performEvaluation(state, state.population,
                                        (GroupedProblemForm)p_problem, SCOflag);
        ((GroupedProblemForm)p_problem).postprocessPopulation(state, state.population);
        // Postprocessing
        //afterEvaluation( state, state.population, (GroupedProblemForm)p_problem );
    }

    
//----------------------------------------------------------------------
// Before evaluation: set elitism, create test point
    public void beforeEvaluation( final EvolutionState state,
                                  final Population population,
                                  final GroupedProblemForm prob )
    {
    }
    
//----------------------------------------------------------------------
// Main computation functions
    public void performEvaluation(final EvolutionState state,
                                  final Population population,
                                  final GroupedProblemForm prob,
                                  final int SCOflag)
    {
        // Compute interaction matrix
        Individual[] pair = new Individual[2];
        boolean[] updates = new boolean[2];
        updates[0] = true;
        updates[1] = true;
        for (int i = 0; i < ntp; i++)
        {
            pair[0] = population.subpops[0].individuals[i];
            for (int j = 0; j<nsol; j++)
            {
                pair[1] = population.subpops[1].individuals[j];
                prob.evaluate(state,pair,updates,false,0);
                interaction[i][j] = ((SimpleFitness)(pair[1].fitness)).fitness();
            }
        }
        //printInteraction();
        
        // Temp array for fitness
        float[] tpFit = new float[population.subpops[0].individuals.length];
        float[] solFit = new float[population.subpops[1].individuals.length];
        
        // Further evaluation
        switch(SCOflag)
        {
            case SCORE_AVERAGE:
                evalAveScore(tpFit, solFit);
                break;
            case SCORE_WEIGHTED:
                evalWeiScore(tpFit, solFit);
                break;
            case SCORE_MOO:
                evalMOOScore(tpFit, solFit);
                break;
			case SCORE_SIMILARITY:
				evalSimScore(tpFit, solFit);
				break;
            case SCORE_AVE_INFO:
                evalAveInfo(tpFit, solFit);
                break;
            case SCORE_WEI_INFO:
                evalWeiInfo(tpFit, solFit);
                break;
            default:
                evalAveScore(tpFit, solFit);
        }
        
        // Assign fitness
        for(int i=0; i<ntp; i++)
        {
            ((SimpleFitness)(population.subpops[0].individuals[i].fitness)).setFitness( state,
                            tpFit[i], false );
        }
        for(int j=0;j<nsol;j++)
        {
            ((SimpleFitness)(population.subpops[1].individuals[j].fitness)).setFitness( state,
                            solFit[j], false );
        }
        
    }  
      
//----------------------------------------------------------------------
// After evaluation: set elitism, create test point
    public void afterEvaluation( final EvolutionState state,
                                 final Population population,
                                 final GroupedProblemForm prob )
    {
    }
    
//----------------------------------------------------------------------
// Various evaluation method
/*--------------------------------------
     Average score
--------------------------------------*/
    public void evalAveScore( final float[] tpFit,
                              final float[] solFit)
    {
        // Sum up row and col of interaction
        float[][] tp_sol = mtxMargin(interaction);
        // tpFitness  = tp_sol[0]
        // solFitness = tp_sol[1]
        // Normalize the score
        float[] nASt = normalizeMax(tp_sol[0]);
        float[] nASs = normalizeMax(tp_sol[1]);
        // Assign fitness
        for(int i=0; i<ntp; i++){tpFit[i] = 1-nASt[i];}
        for(int j=0;j<nsol;j++) {solFit[j] = nASs[j]; }
    }
        
/*--------------------------------------
     Weighted score
--------------------------------------*/
    public void evalWeiScore( final float[] tpFit,
                              final float[] solFit)
    {
        // Sum up row and col of interaction
        float[][] wt_tp_sol = mtxMargin(interaction);
        // wtTP[i]  = 1 / sum_i(wt_tp_sol[0][i])
        // wtSol[j] = 1 / sum_j(wt_tp_sol[1][j])
        // Avoid divided by 0.0
        for(int i=0; i<ntp; i++) 
        {
            if(wt_tp_sol[0][i] ==0) 
                wt_tp_sol[0][i]  = 0F;
            else
                wt_tp_sol[0][i]  = 1/wt_tp_sol[0][i];
        }
        for(int j=0; j<nsol;j++) 
        {
            if(wt_tp_sol[1][j] ==0) 
                wt_tp_sol[1][j]  = 0F;
            else
                wt_tp_sol[1][j]  = 1/wt_tp_sol[1][j];
        }
        // Normalize the weights
        wt_tp_sol[0] = normalizeSumToOne(wt_tp_sol[0]);
        wt_tp_sol[1] = normalizeSumToOne(wt_tp_sol[1]);
        // tpFitness[i]  = sum_j (wtSol[j]*interaction[i][j])
        // solFitness[j] = sum_i (wtTP[i] *interaction[i][j])
        float[] tpFitness = new float[ntp];
        float[] solFitness = new float[nsol];
        // Take weighted avarage
        for(int i=0; i<ntp; i++)
        {
            for(int j=0;j<nsol;j++)
            {
                tpFitness[i] += interaction[i][j]*wt_tp_sol[1][j];
            }
        }
        for(int j=0;j<nsol;j++)
        {
            for(int i=0; i<ntp; i++)
            {
                solFitness[j] += interaction[i][j]*wt_tp_sol[0][i];
            }
        }
        // Normalize the score
        float[] nASt = normalizeMax(tpFitness);
        float[] nASs = normalizeMax(solFitness);
        // Assign fitness
        for(int i=0; i<ntp; i++){tpFit[i] = 1-nASt[i];}
        for(int j=0;j<nsol;j++) {solFit[j] = nASs[j]; }
    }

/*--------------------------------------
     MO score
--------------------------------------*/
    public void evalMOOScore( final float[] tpFit,
                              final float[] solFit)
    {
        // Compute dominance matrix: an entry of -1 at (i,j) means "i is dominated by j".
        float[][] domTP = calDominance(interaction);
        float[][] domSol= calDominance(transposeMatrix(interaction));
        // Set the fitness as "number of individuals that dominates it"
        float[] domScoreTP = mtxMargin(domTP)[0];
        float[] domScoreSol = mtxMargin(domSol)[0];
        // Print for testing
        //printArray(domScoreTP);
        //printArray(domScoreSol);
        System.out.println("NonDomTP: "+ zeroCount(domScoreTP)+"    NonDomSol: "+ zeroCount(domScoreSol));
        // Normalize domScore
        float[] ndscoreTP = normalizeMax(domScoreTP);
        float[] ndscoreSol = normalizeMax(domScoreSol);
        // Assign fitness
        for(int i=0; i<ntp; i++){tpFit[i] = ndscoreTP[i];}
        for(int j=0;j<nsol;j++) {solFit[j] = ndscoreSol[j];}
    }
        
/*--------------------------------------
     Similarity
--------------------------------------*/
    public void evalSimScore( final float[] tpFit,
                              final float[] solFit)
    {
    // This method has been abandoned
    }

/*--------------------------------------
     Average informativeness
--------------------------------------*/
    public void evalAveInfo( final float[] tpFit,
                             final float[] solFit)
    {
        // Weight for Ave-Score
        float weiAS = 0.7F;
        // Sum up row and col of interaction as ave-score
        float[][] tp_sol = mtxMargin(interaction);
        //Compute distinction matrix
        float[][][] distTP = calTPDistinction();
        float[][][] distSol= calSolDistinction();
        //Compute d-score: # of distinction it makes
        // Average distinction
        float[] dscoreTP = new float[ntp];
        float[] dscoreSol = new float[nsol];
        for(int i=0; i<ntp; i++)
        {
            for(int p=0;p<nsol;p++){
            for(int q=0;q<nsol;q++){
                dscoreTP[i] += distTP[i][p][q];
            }
            }
        }
        for(int j=0;j<nsol;j++)
        {
            for(int p=0;p<ntp;p++){
            for(int q=0;q<ntp;q++){
                dscoreSol[j] += distSol[j][p][q];
            }
            }
        }
        // Normalize the distinction-score
        float[] ndscoreTP = normalizeMax(dscoreTP);
        float[] ndscoreSol = normalizeMax(dscoreSol);
        // Assign fitness
        for(int i=0; i<ntp; i++)
        {
            tpFit[i] = (1-weiAS)*ndscoreTP[i] + weiAS*(1-tp_sol[0][i]/(float)nsol);
        }
        for(int j=0;j<nsol;j++)
        {
            solFit[j] = (1-weiAS)*ndscoreSol[j] + weiAS*(tp_sol[1][j]/(float)ntp);
        }
    }
/*--------------------------------------
     Weighted informativeness 
--------------------------------------*/
    public void evalWeiInfo(final float[] tpFit,
                            final float[] solFit)
    {
        // Weight for Ave-Score
        float weiAS = 0.7F;
        // Sum up row and col of interaction as ave-score
        float[][] tp_sol = mtxMargin(interaction);
        //Compute distinction matrix
        float[][][] distTP = calTPDistinction();
        float[][][] distSol= calSolDistinction();
        // Compute weights: how many inds have made this distinction?
        float[][] weiTP = new float[nsol][nsol];
        float[][] weiSol= new float[ntp][ntp];
        for(int i=0; i<ntp; i++)
        {
            for(int p=0;p<nsol;p++){
            for(int q=0;q<nsol;q++){
                weiTP[p][q] += distTP[i][p][q];
            }
            }        
        }
        for(int j=0;j<nsol;j++)
        {
            for(int p=0;p<ntp;p++){
            for(int q=0;q<ntp;q++){
                weiSol[p][q] += distSol[j][p][q];
            }
            }        
        }
        //Compute d-score:
        // Weighted distinction
        float[] dscoreTP = new float[ntp];
        float[] dscoreSol = new float[nsol];
        for(int i=0; i<ntp; i++)
        {
            for(int p=0;p<nsol;p++){
            for(int q=0;q<nsol;q++){
                if(weiTP[p][q]==0)weiTP[p][q]=1;            // Avoid divided by zero
                dscoreTP[i] += distTP[i][p][q]/weiTP[p][q];
            }
            }
        }
        for(int j=0;j<nsol;j++)
        {
            for(int p=0;p<ntp;p++){
            for(int q=0;q<ntp;q++){
                if(weiSol[p][q]==0)weiSol[p][q]=1;          // Avoid divided by zero
                dscoreSol[j] += distSol[j][p][q]/weiSol[p][q];
            }
            }
        }
        // Normalize the distinction-score
        float[] ndscoreTP = normalizeMax(dscoreTP);
        float[] ndscoreSol = normalizeMax(dscoreSol);
        // Assign fitness
        for(int i=0; i<ntp; i++)
        {
            tpFit[i] = (1-weiAS)*ndscoreTP[i] + weiAS*(1-tp_sol[0][i]/(float)nsol);
        }
        for(int j=0;j<nsol;j++)
        {
            solFit[j] = (1-weiAS)*ndscoreSol[j] + weiAS*(tp_sol[1][j]/(float)ntp);
        }
    }
//----------------------------------------------------------------------
/* Advanced MO score: given number of objectives and raw scores
     rawScore: a square matrix (n*numObj) where each row represents the scores 
               of one individual on all objectives.
     MOScore : an array with length of n that contains "number of individuals that dominates it"
               (normalized) as the MO scores.
*/
    public void advMOEval( final float[][] rawScore,
                           final float[] MOScore)
    {
        // Compute dominance matrix: an entry of -1 at (i,j) means "i is dominated by j".
        float[][] dom = calDominance(rawScore);
        // Set the fitness as "number of individuals that dominates it"
        float[] domScore = mtxMargin(dom)[0];
        // Print for testing
        //System.out.println("NonDomTP: "+ zeroCount(domScoreTP)+"    NonDomSol: "+ zeroCount(domScoreSol));
        // Normalize domScore
        float[] ndScore = normalizeMax(domScore);
        // Assign fitness
        for(int i=0; i<MOScore.length; i++){MOScore[i] = ndScore[i];}
    }
//----------------------------------------------------------------------
// Supplementary functions
    // Print out interaction matrix
    public void printInteraction()
    {
        for(int j=0;j<nsol;j++)
        {
            System.out.print("sol "+j+": ");
            for(int i=0; i<ntp; i++)
            {
                System.out.print(interaction[i][j]+", ");
            }
            System.out.print("\n");
        }
    }

    // Print out a float array
    public void printArray(float[] a)
    {
        int n = a.length;
        for(int j=0;j<n;j++)
        {
            System.out.print(a[j]+", ");
        }
        System.out.print("\n");
    }

    // Tranpose a square matrix
    public float[][] transposeMatrix(float[][] a)
    {
        int n = a.length;
        int m = a[0].length;
        float[][] b = new float[m][n];
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                b[j][i] = a[i][j];
            }
        }
        return(b);
    }

    // Return the number of zeros in a float array
    public float zeroCount(float[] a)
    {
        int n = a.length;
        int nz = 0;
        for(int j=0;j<n;j++)
        {
            nz+= (a[j]==0 ? 1 : 0);
        }
        return((float)nz / (float)n);
    }

    // Compute marginal distribution of given matrix (rectangular matrix required)
    public float[][] mtxMargin(final float[][] mtx)
    {
        // dimentions
        int nrow = mtx.length;
        int ncol = mtx[0].length;
        //
        float[] margCol = new float[ncol];
        float[] margRow = new float[nrow];
        
        for(int j=0;j<ncol;j++)
        {
            for(int i=0; i<nrow; i++)
            {
                margCol[j] += mtx[i][j];
            }
        }
        for(int i=0; i<nrow; i++)
        {
            for(int j=0; j<ncol; j++)
            {
                margRow[i] += mtx[i][j];
            }
        }
        // Return
        float[][] margin = new float[2][];
        margin[0] = margRow;
        margin[1] = margCol;
        return(margin);
    }
    
    // Compute dominance matrix from the rawScore
    public float[][] calDominance(final float[][] rawScore)
    {
        // dimentions
        int nInd = rawScore.length;
        int nObj = rawScore[0].length;
        float[][] dom = new float[nInd][nInd];
        
        // Pairwise comparison
        for(int i=0;i<nInd;i++)
        {
            for(int j=(i+1); j<nInd; j++)
            {
                dom[i][j] = (float)checkDominance(rawScore[i],rawScore[j]);
                // Convert to only 0s and -1s (non-dominated/dominated)
                if(dom[i][j]==1){
                    dom[j][i] = -1;
                    dom[i][j] = 0;
                }
            }
        }
        // Return
        return(dom);
    }
        
    // Check Pareto dominance for given two float arrays
    //   return values:
    //     1: a1 dominates a2
    //     0: neither is dominated
    //    -1: a2 dominates a1
    public int checkDominance(final float[] a1, final float[] a2)
    {
        // Initialization 
        boolean A1DomA2 = ((a1[0]-a2[0])>0);
        boolean A2DomA1 = ((a1[0]-a2[0])<0);
        // Check for each attributes
        for(int i=1; i< a1.length; i++){
            if(A1DomA2){
                if((a1[i]-a2[i])<0){            // break if incocsistent
                    A1DomA2 = false;
                    break;
                 }
           } else if(A2DomA1){
                if((a1[i]-a2[i])>0){            // break if incocsistent
                    A2DomA1 = false;
                    break;
                }
            } else {
                A1DomA2 = ((a1[i]-a2[i])>0);
                A2DomA1 = ((a1[i]-a2[i])<0);
            }
        }
        // Return
        if(A1DomA2)
            return(1);
        else if(A2DomA1)
            return(-1);
        else
            return(0); 
    }
    
    // Compute distinction matrix for test-points(from given interaction matrix)
    public float[][][] calTPDistinction()
    {
        // dimentions
        float[][][] dist = new float[ntp][nsol][nsol];
        
        for(int i=0;i<ntp;i++)
        {
            for(int j=2; j<nsol; j++)
            {
            for(int k=0; k<nsol; k++)
            {
                dist[i][j][k] = ((interaction[i][j] > interaction[i][k])? 1:0);
            }
            }
        }
        // Return
        return(dist);
    }
    
    // Compute distinction matrix for solutions(from given interaction matrix)
    public float[][][] calSolDistinction()
    {
        // dimentions
        float[][][] dist = new float[nsol][ntp][ntp];
        
        for(int i=0;i<nsol;i++)
        {
            for(int j=2; j<ntp; j++)
            {
            for(int k=0; k<ntp; k++)
            {
                dist[i][j][k] = ((interaction[j][i] > interaction[k][i])? 1:0);
            }
            }
        }
        // Return
        return(dist);
    }
    
    // Sum up a float array
    public float sumArray(final float[] a1)
    {
        float sum = 0;
        for (int i = 0; i < a1.length; i++){
            sum += a1[i];
        }
        return(sum);
    }
    
    // Normalize a float array by its maximum and minimum value
    public float[] normalizeMax(final float[] a1)
    {
        float maxA = -1.0e15F;
        float minA = 1.0e15F;
        float[] a2 = new float[a1.length];
        for (int i = 0; i < a1.length; i++){
            maxA = Math.max(maxA, a1[i]);
            minA = Math.min(minA, a1[i]);
        }
        float distA = (((maxA-minA)==0)? 1:(maxA-minA)); // avoid divid-by-zero
        for (int i = 0; i < a2.length; i++){
            a2[i] = (a1[i]-minA)/distA;
        }
        return(a2);
    }
    
    // Normalize a float array to have a total sum of 1
    public float[] normalizeSumToOne(final float[] a1)
    {
        float sum = sumArray(a1);
        float[] a2 = new float[a1.length];
        for (int i = 0; i < a2.length; i++){
            a2[i] = a1[i]/sum;
        }
        return(a2);
    }
}
